#pragma once

#include "jsi/jsi.h"
#include "jsi/jsilib.h"
#include <array>
#include <atomic>
#include <condition_variable>
#include <cstdlib>
#include <deque>
#include <mutex>
#include <sstream>
#include <thread>

#include "JavaScriptCore/JavaScript.h"

namespace jsc
{
using namespace facebook::jsi;
    class JSCRuntime : public Runtime
    
    {
    public:
        // Creates new context in new context group
        JSCRuntime();
        // Retains ctx
        JSCRuntime(JSGlobalContextRef ctx);
        ~JSCRuntime();

        std::shared_ptr<const PreparedJavaScript> prepareJavaScript(
            const std::shared_ptr<const Buffer> &buffer,
            std::string sourceURL) override;

        Value evaluatePreparedJavaScript(
            const std::shared_ptr<const PreparedJavaScript> &js) override;

        Value evaluateJavaScript(
            const std::shared_ptr<const Buffer> &buffer,
            const std::string &sourceURL) override;

        // If we use this interface to implement microtasks in the host we need to
        // polyfill `Promise` to ujsvmse these methods, because JSC doesn't currently
        // support providing a custom queue for its built-in implementation.
        // Not doing this would result in a non-compliant behavior, as microtasks
        // wouldn't execute in the order in which they were queued.
        void queueMicrotask(const Function &callback);
        bool drainMicrotasks(int maxMicrotasksHint = -1) override;

        Object global() override;

        std::string description() override;

        bool isInspectable() override;

        void setDescription(const std::string &desc);

        // Please don't use the following two functions, only exposed for
        // integration efforts.
        JSGlobalContextRef getContext()
        {
            return ctx_;
        }

        // JSValueRef->JSValue (needs make.*Value so it must be member function)
        Value createValue(JSValueRef value) const;

        // Value->JSValueRef (similar to above)
        JSValueRef valueRef(const Value &value);

    protected:
         
        class JSCSymbolValue final : public PointerValue
        {
#ifndef NDEBUG
            JSCSymbolValue(
                JSGlobalContextRef ctx,
                const std::atomic<bool> &ctxInvalid,
                JSValueRef sym,
                std::atomic<intptr_t> &counter);
#else
            JSCSymbolValue(
                JSGlobalContextRef ctx,
                const std::atomic<bool> &ctxInvalid,
                JSValueRef sym);
#endif
            void invalidate() noexcept override;

            JSGlobalContextRef ctx_;
            const std::atomic<bool> &ctxInvalid_;
            // There is no C type in the JSC API to represent Symbol, so this stored
            // a JSValueRef which contains the Symbol.
            JSValueRef sym_;
#ifndef NDEBUG
            std::atomic<intptr_t> &counter_;
#endif
        protected:
            friend class JSCRuntime;
        };

        class JSCStringValue final : public PointerValue
        {
#ifndef NDEBUG
            JSCStringValue(JSStringRef str, std::atomic<intptr_t> &counter);
#else
            JSCStringValue(JSStringRef str);
#endif
            void invalidate() noexcept override;

            JSStringRef str_;
#ifndef NDEBUG
            std::atomic<intptr_t> &counter_;
#endif
        protected:
            friend class JSCRuntime;
        };

        class JSCObjectValue final : public PointerValue
        {
            JSCObjectValue(
                JSGlobalContextRef ctx,
                const std::atomic<bool> &ctxInvalid,
                JSObjectRef obj
#ifndef NDEBUG
                ,
                std::atomic<intptr_t> &counter
#endif
            );

            void invalidate() noexcept override;

            JSGlobalContextRef ctx_;
            const std::atomic<bool> &ctxInvalid_;
            JSObjectRef obj_;
#ifndef NDEBUG
            std::atomic<intptr_t> &counter_;
#endif
        protected:
            friend class JSCRuntime;
        };

        PointerValue *cloneSymbol(const Runtime::PointerValue *pv) override;
        PointerValue *cloneBigInt(const Runtime::PointerValue *pv) override;
        PointerValue *cloneString(const Runtime::PointerValue *pv) override;
        PointerValue *cloneObject(const Runtime::PointerValue *pv) override;
        PointerValue *clonePropNameID(const Runtime::PointerValue *pv) override;

        PropNameID createPropNameIDFromAscii(const char *str, size_t length)
            override;
        PropNameID createPropNameIDFromUtf8(const uint8_t *utf8, size_t length)
            override;
        PropNameID createPropNameIDFromString(const String &str) override;
        PropNameID createPropNameIDFromSymbol(const Symbol &sym) override;
        std::string utf8(const PropNameID &) override;
        bool compare(const PropNameID &, const PropNameID &) override;

        std::string symbolToString(const Symbol &) override;

        BigInt createBigIntFromInt64(int64_t) override;
        BigInt createBigIntFromUint64(uint64_t) override;
        bool bigintIsInt64(const BigInt &) override;
        bool bigintIsUint64(const BigInt &) override;
        uint64_t truncate(const BigInt &) override;
        String bigintToString(const BigInt &, int) override;

        String createStringFromAscii(const char *str, size_t length) override;
        String createStringFromUtf8(const uint8_t *utf8, size_t length) override;
        std::string utf8(const String &) override;

        Object createObject() override;
        Object createObject(std::shared_ptr<HostObject> ho) override;
        virtual std::shared_ptr<HostObject> getHostObject(
            const Object &) override;
        HostFunctionType &getHostFunction(const Function &) override;

        bool hasNativeState(const Object &) override;
        std::shared_ptr<NativeState> getNativeState(const Object &) override;
        void setNativeState(const Object &, std::shared_ptr<NativeState>)
            override;

        Value getProperty(const Object &, const String &name) override;
        Value getProperty(const Object &, const PropNameID &name)
            override;
        bool hasProperty(const Object &, const String &name) override;
        bool hasProperty(const Object &, const PropNameID &name) override;
        void setPropertyValue(
            const Object &,
            const String &name,
            const Value &value) override;
        void setPropertyValue(
            const Object &,
            const PropNameID &name,
            const Value &value) override;
        bool isArray(const Object &) const override;
        bool isArrayBuffer(const Object &) const override;
        bool isFunction(const Object &) const override;
        bool isHostObject(const Object &) const override;
        bool isHostFunction(const Function &) const override;
        Array getPropertyNames(const Object &) override;

        // TODO: revisit this implementation
        WeakObject createWeakObject(const Object &) override;
        Value lockWeakObject(const WeakObject &) override;

        Array createArray(size_t length) override;
        ArrayBuffer createArrayBuffer(
            std::shared_ptr<MutableBuffer> buffer) override;
        size_t size(const Array &) override;
        size_t size(const ArrayBuffer &) override;
        uint8_t *data(const ArrayBuffer &) override;
        Value getValueAtIndex(const Array &, size_t i) override;
        void setValueAtIndexImpl(const Array &, size_t i, const Value &value)
            override;

        Function createFunctionFromHostFunction(
            const PropNameID &name,
            unsigned int paramCount,
            HostFunctionType func) override;
        Value call(
            const Function &,
            const Value &jsThis,
            const Value *args,
            size_t count) override;
        Value callAsConstructor(
            const Function &,
            const Value *args,
            size_t count) override;

        bool strictEquals(const Symbol &a, const Symbol &b) const override;
        bool strictEquals(const BigInt &a, const BigInt &b) const override;
        bool strictEquals(const String &a, const String &b) const override;
        bool strictEquals(const Object &a, const Object &b) const override;
        bool instanceOf(const Object &o, const Function &f) override;
        void setExternalMemoryPressure(const Object &, size_t) ;

    private:
        // Basically convenience casts
        static JSValueRef symbolRef(const Symbol &str);
        static JSStringRef stringRef(const String &str);
        static JSStringRef stringRef(const PropNameID &sym);
        static JSObjectRef objectRef(const Object &obj);
        static JSObjectRef objectRef(const WeakObject &obj);

        // Factory methods for creating String/Object
        Symbol createSymbol(JSValueRef symbolRef) const;
        String createString(JSStringRef stringRef) const;
        PropNameID createPropNameID(JSStringRef stringRef);
        Object createObject(JSObjectRef objectRef) const;

        // Used by factory methods and clone methods
        Runtime::PointerValue *makeSymbolValue(JSValueRef sym) const;
        Runtime::PointerValue *makeStringValue(JSStringRef str) const;
        Runtime::PointerValue *makeObjectValue(JSObjectRef obj) const;

        JSValueRef getNativeStateSymbol();

        void checkException(JSValueRef exc);
        void checkException(JSValueRef res, JSValueRef exc);
        void checkException(JSValueRef exc, const char *msg);
        void checkException(JSValueRef res, JSValueRef exc, const char *msg);

        JSGlobalContextRef ctx_;
        std::atomic<bool> ctxInvalid_;
        std::string desc_;
        JSValueRef nativeStateSymbol_ = nullptr;
        std::deque<Function> microtaskQueue_;
#ifndef NDEBUG
        mutable std::atomic<intptr_t> objectCounter_;
        mutable std::atomic<intptr_t> symbolCounter_;
        mutable std::atomic<intptr_t> stringCounter_;
#endif
    };

namespace detail
    {

        class ArgsConverter
        {
        public:
            ArgsConverter(JSCRuntime &rt, const Value *args, size_t count)
            {
                JSValueRef *destination = inline_;
                if (count > maxStackArgs)
                {
                    outOfLine_ = std::make_unique<JSValueRef[]>(count);
                    destination = outOfLine_.get();
                }

                for (size_t i = 0; i < count; ++i)
                {
                    destination[i] = rt.valueRef(args[i]);
                }
            }

            operator JSValueRef *()
            {
                return outOfLine_ ? outOfLine_.get() : inline_;
            }

        private:
            constexpr static unsigned maxStackArgs = 8;
            JSValueRef inline_[maxStackArgs];
            std::unique_ptr<JSValueRef[]> outOfLine_;
        };
    } // namespace detail
}